package com.its.search.service.impl;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.its.goods.pojo.Goods;
import com.its.search.domain.GoodsEs;
import com.its.search.domain.GoodsSearchParam;
import com.its.search.domain.GoodsSearchResult;
import com.its.search.feign.GoodsFeign;
import com.its.search.service.SearchService;
import lombok.SneakyThrows;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.AnalyzeRequest;
import org.elasticsearch.client.indices.AnalyzeResponse;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.SortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.search.suggest.Suggest;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.elasticsearch.search.suggest.SuggestBuilders;
import org.elasticsearch.search.suggest.completion.CompletionSuggestion;
import org.elasticsearch.search.suggest.completion.CompletionSuggestionBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.*;

@Service
public class SearchServiceImpl implements SearchService {

    //1.依赖注入远程调用的接口
    @Autowired
    private GoodsFeign goodsFeign;

    //2.依赖注入Es的Bean对象
    @Autowired
    private ElasticsearchRestTemplate elasticsearchTemplate;
    @Autowired
    private RestHighLevelClient restHighLevelClient;


    /*
    * 1. 初始化ES库中的数据:
    *      a. 远程调用商品管理服务: 获取当前MySQL数据库中所有的商品及其详情信息
    *      b. 将List<Goods>转换为List<GoodsEs>
           c. 使用SpringDataElasticsearch框架，将数据存储到ES库中
       注意:
       *    该方法只在初始化的时候执行一次，执行完毕后就可以注释了！
    * */
    @Override
    public void syncGoodsToES() {
        //a.远程调用商品管理服务：获取所有的商品及其详细信息
        List<Goods> listGoods = goodsFeign.findAll();
        //b.将List<Goods>转换为List<GoodsEs>
        List<GoodsEs> goodsEsList = new ArrayList<>();
        for (Goods goods : listGoods) {
            //创建GoodsEs对象来存储以前的数据
            GoodsEs goodsEs = new GoodsEs();
            //b.1将商品的基本信息存储到goodsEs对象中
            goodsEs.setGid(goods.getGid());//商品ID
            goodsEs.setGoodsName(goods.getGoodsName());//商品的名称
            goodsEs.setGoodsPrice(String.valueOf(goods.getGoodsPrice()));//商品的价格
            goodsEs.setGoodsType(goods.getGoodsType());//商品类型
            goodsEs.setGoodsInventory(String.valueOf(goods.getGoodsInventory()));//商品库存
            //b.2存储商品的词条，用于自动补全
            List<String> tags = new ArrayList<>();
            tags.addAll(analyze(goods.getGoodsName(),"ik_smart"));//商品名称的字条
            tags.addAll(analyze(goods.getGoodsType(),"ik_smart"));//商品名称的字条
            //存储集合
            goodsEsList.add(goodsEs);
        }
        //c.存储到ES库中
        elasticsearchTemplate.save(goodsEsList);
    }

    //词条生成方法: 获取指定文档的词条数据
    @SneakyThrows
    public List<String> analyze(String goodsName, String ikSmart) {
        //1. 封装请求数据
        AnalyzeRequest analyzeRequest=AnalyzeRequest.withIndexAnalyzer("goods",ikSmart,goodsName);
        //2. 完成分词
        AnalyzeResponse response = restHighLevelClient.indices().analyze(analyzeRequest, RequestOptions.DEFAULT);
        //3. 封装结果
        List<AnalyzeResponse.AnalyzeToken> tokens = response.getTokens();
        List<String> tags=new ArrayList<>();
        for (AnalyzeResponse.AnalyzeToken token : tokens) {
            tags.add(token.getTerm());
        }
        return tags;
    }

    /*
     * 自动补全搜索关键词
     *  ES的词条补全的底层逻辑
     *  1. 对文档中的tags字段的值进行分词获取词条的集合（注意: 使用的是tags字段指定的分词器完成）
     *  2. 去除词条中的重复词汇
     *  3. 使用请求中的搜索前缀匹配词汇
     *  4. 返回结果的前N条
     * */
    @SneakyThrows //@SneakyThrows是lombok的注解，该方法在编译的时候会自动添加try-catch语句
    @Override
    public List<String> autoSuggest(String keyword) {
        //1.封装查询条件
        SearchRequest searchRequest=new SearchRequest("goods");
        //创建词条工具条件对象
        CompletionSuggestionBuilder completionSuggestionBuilder = SuggestBuilders.completionSuggestion("tags").text(keyword).skipDuplicates(true).size(10);
        //创建词条对象
        SuggestBuilder suggestBuilder=new SuggestBuilder();
        suggestBuilder.addSuggestion("prefix_suggestion",completionSuggestionBuilder);
        //存储词条对象
        SearchSourceBuilder searchSourceBuilder=new SearchSourceBuilder();
        searchSourceBuilder.suggest(suggestBuilder);
        searchRequest.source(searchSourceBuilder);

        //2.调用ES的client中的search方法
        SearchResponse search = restHighLevelClient.search(searchRequest,RequestOptions.DEFAULT);
        //3. 封装结果
        List<String> list=new ArrayList<>();
        Suggest suggest = search.getSuggest();
        Suggest.Suggestion<? extends Suggest.Suggestion.Entry<? extends Suggest.Suggestion.Entry.Option>> suggestSuggestion = suggest.getSuggestion("prefix_suggestion");
        List<? extends Suggest.Suggestion.Entry<? extends Suggest.Suggestion.Entry.Option>> entries = suggestSuggestion.getEntries();
        for (Suggest.Suggestion.Entry entry : entries) {
            List<CompletionSuggestion.Entry.Option> options = entry.getOptions();
            for (CompletionSuggestion.Entry.Option option : options) {
                String text = option.getText().string();
                list.add(text);
            }
        }
        return list;
    }

    /*
     * 搜索商品
     *
     * */
    @Override
    public GoodsSearchResult searchGoodsService(GoodsSearchParam param) {
        //1. 封装搜索条件
        Query query = searchCondition(param);
        //2. 完成搜索
        SearchHits<GoodsEs> search = elasticsearchTemplate.search(query, GoodsEs.class);
        //3. 封装结果
        //获取查询结果
        List<GoodsEs> goodsESList = new ArrayList<>();
        List<SearchHit<GoodsEs>> searchHits = search.getSearchHits();
        for (SearchHit<GoodsEs> searchHit : searchHits) {
            goodsESList.add(searchHit.getContent());
        }
        //将搜索结果封装到MyBatis-Plus的page对象中
        Page<GoodsEs> page=new Page<>();
        page.setRecords(goodsESList);
        page.setTotal(search.getTotalHits());
        page.setCurrent(param.getPage());
        page.setSize(param.getSize());
        //创建GoodsSearchResult对象封装查询结果
        GoodsSearchResult result=new GoodsSearchResult();
        result.setGoodsPage(page);
        result.setGoodsSearchParam(param);
        searchPanelData(result,param);
        return result;
    }

    //封装方法: 生成搜索面板数据
    public void searchPanelData(GoodsSearchResult result,GoodsSearchParam param){
        //获取查询条件
        param.setSize(20);
        param.setPage(1);
        param.setSortFiled(null);
        param.setSort(null);
        Query query = searchCondition(param);
        //执行查询
        SearchHits<GoodsEs> search = elasticsearchTemplate.search(query, GoodsEs.class);
        List<GoodsEs> content = new ArrayList();
        for (SearchHit<GoodsEs> goodsESSearchHit : search) {
            GoodsEs goodsES = goodsESSearchHit.getContent();
            content.add(goodsES);
        }

        // 4.遍历集合，封装查询面板
        // 商品相关的类型列表
        Set<String> goodsType = new HashSet();

        for (GoodsEs goodsES : content) {

            // 获取类型
            goodsType.add(goodsES.getGoodsType());
        }
        result.setProductType(goodsType);
    }

    //封装搜索条件
    public Query searchCondition(GoodsSearchParam param){
        //创建条件构建对象
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        //1. 封装搜索条件
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        //判断如果有检索条件则按照检索条件查询没有则查询全部
        if(!StringUtils.hasText(param.getKeyword())){
            boolQueryBuilder.must(QueryBuilders.matchAllQuery());
        }else{
            boolQueryBuilder.must(QueryBuilders.multiMatchQuery(param.getKeyword(),"goodsName"));
        }

        //如果有价格则按照价格进行范围筛选
        Double lowPrice = param.getLowPrice();
        Double highPrice = param.getHighPrice();
        if(lowPrice!=null &&lowPrice >0){
            boolQueryBuilder.must(QueryBuilders.rangeQuery("goodsPrice").gte(lowPrice));
        }
        if(highPrice!=null &&highPrice >0){
            boolQueryBuilder.must(QueryBuilders.rangeQuery("goodsPrice").lte(highPrice));
        }

        nativeSearchQueryBuilder.withQuery(boolQueryBuilder);
        //2. 封装分页条件
        PageRequest pageRequest=PageRequest.of(param.getPage()-1, param.getSize());
        nativeSearchQueryBuilder.withPageable(pageRequest);
        //3. 封装排序条件
        String sortFiled = param.getSortFiled();//排序字段
        String sort = param.getSort();//排序方式
        SortBuilder sortBuilder=null;
        if(StringUtils.hasText(sortFiled) && StringUtils.hasText(sort)){
            sortBuilder = SortBuilders.fieldSort(sortFiled).order(SortOrder.fromString(sort));
            nativeSearchQueryBuilder.withSort(sortBuilder);
        }
        //构建条件对象
        Query query=nativeSearchQueryBuilder.build();
        return query;
    }

    //删除ES库中的商品文档数据
    @Override
    public void deleteGoodsService(Long id) {
        elasticsearchTemplate.delete(id+"", GoodsEs.class);
    }

    //新增商品文档
    @Override
    public void syncGoodsToES(Goods goods) {
        //创建GoodsEs对象存储文档数据
        GoodsEs goodsEs = new GoodsEs();
        //b.1 将商品的基本信息存储到goodsES对象中
        goodsEs.setGid(goods.getGid()); //商品的ID
        goodsEs.setGoodsName(goods.getGoodsName());//商品的名称
        goodsEs.setGoodsType(goods.getGoodsType());//商品的类型
        goodsEs.setGoodsPrice(String.valueOf(goods.getGoodsPrice()));//商品的价格
        goodsEs.setGoodsImages(goods.getGoodsImages());//商品的图片
        //b.2 存储商品的词条(自动补全的时候)
        List<String> tags=new ArrayList<>();
        tags.addAll(analyze(goodsEs.getGoodsName(), "ik_smart"));//商品名称的词条
        tags.addAll(analyze(goodsEs.getGoodsType(), "ik_smart"));//商品类型的词条
        goodsEs.setTags(tags);
        //c. 存储到ES库中
        elasticsearchTemplate.save(goodsEs);
    }
}
